/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package audio.player;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

/**
 *
 * @author głodoś
 */
public class SimpleAudioPlayer {
    public static final int STATE_NEW=0,
                            STATE_IDLE=1,
                            STATE_PLAYING=2,
                            STATE_PAUSED=3;

    private int state;

    private AudioFormat format;
    private byte[] samples;
    private InputStream source;
    private FilterWrapper filter;

    private boolean stopFlag, sleepFlag;
    private final Object sleepLock;

    private ProgressListener l;
    private long totalLength;
    /**
     * Opens a sound from a file.
     */
    public SimpleAudioPlayer() {
        sleepLock = new Object();
        filter = new FilterWrapper();
        state = STATE_NEW;
    }

    public void setSource(File file) throws UnsupportedAudioFileException, IOException{
        AudioInputStream stream = AudioSystem.getAudioInputStream(file);
        format = stream.getFormat();
        //totalLength = (Long)AudioSystem.getAudioFileFormat(file).getProperty("duration");
        // get the audio samples
        samples = getSamples(stream);
        state = STATE_IDLE;
    }

    public void setProgressListener(ProgressListener l){
        this.l = l;
    }

    /**
     * Gets the samples of this sound as a byte array.
     */
    public byte[] getSamples() {
        return samples;
    }

    public void setFilter(SoundFilter filter){
        this.filter.setFilter(filter);
    }

    /**
     * Gets the samples from an AudioInputStream as an array of bytes.
     */
    private byte[] getSamples(AudioInputStream audioStream) {
        // get the number of bytes to read
        int length = (int) (audioStream.getFrameLength() * format.getFrameSize());

        // read the entire stream
        byte[] samples = new byte[length];
        DataInputStream is = new DataInputStream(audioStream);
        try {
            is.readFully(samples);
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        // return the samples
        return samples;
    }

    public void pause(){
        if(state==STATE_PLAYING){
            sleepFlag = true;
        }
    }
    public void stop(){
        if(state==STATE_PLAYING){
            stopFlag = true;
        }
    }

    /**
     * Plays a stream. This method blocks (doesn't return) until the sound is
     * finished playing.
     */
    public void play() throws InvalidStateException{
        switch(state){
            case STATE_NEW:
                throw new InvalidStateException("Nie wybrano pliku do odtwarzania.");
            case STATE_PLAYING:
                return;
            case STATE_PAUSED:
                synchronized(sleepLock){
                    sleepFlag = false;
                    state = STATE_PLAYING;
                    sleepLock.notify();
                }
                return;
        }

        state = STATE_PLAYING;
        stopFlag = false;
        new Thread() {

            public void run() {
                // use a short, 100ms (1/10th sec) buffer for real-time
                // change to the sound stream
                source = new FilteredAudioStream(new ByteArrayInputStream(samples), filter);
                int bufferSize = format.getFrameSize()
                        * Math.round(format.getSampleRate() / 10);
                byte[] buffer = new byte[bufferSize];

                // create a line to play to
                SourceDataLine line;
                try {
                    DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
                    line = (SourceDataLine) AudioSystem.getLine(info);

                    line.open(format, bufferSize);
                } catch (LineUnavailableException ex) {
                    ex.printStackTrace();
                    return;
                }

                // start the line
                line.start();
                // copy data to the line
                try {
                    int numBytesRead = 0;
                    while (numBytesRead != -1) {
                        numBytesRead = source.read(buffer, 0, buffer.length);
                        if(stopFlag || Thread.interrupted())
                            break;
                        if(sleepFlag){
                            synchronized(sleepLock){
                                state = STATE_PAUSED;
                                sleepLock.wait();
                            }
                        }
                        if(l!=null){
                            l.onProgress(line.getMicrosecondPosition(), totalLength);
                        }
                        if (numBytesRead != -1) {
                            line.write(buffer, 0, numBytesRead);
                        }
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                }catch (InterruptedException ex) {
                    ex.printStackTrace();
                }

                // wait until all data is played, then close the line
                line.drain();
                line.close();
                state = STATE_IDLE;
            }
        }.start();


    }

    public static class InvalidStateException extends Exception{
        public InvalidStateException() {
            super();
        }
        public InvalidStateException(String msg) {
            super(msg);
        }
    }

    public interface ProgressListener{
        public void onPlay();
        public void onProgress(long elapsed, long total);
        public void onPause();
        public void onStop();
    }
}
